package algorithm.sort.select;

import algorithm.sort.base.ArrayUtils;

import java.util.ArrayDeque;
import java.util.Deque;

public class StateEventSelectSort {
    public static void sort(int[] data) {
        Deque<Event> eventDeque = new ArrayDeque<>();
        eventDeque.push(new InitEvent(data));
        while (!eventDeque.isEmpty()) {
            Event event = eventDeque.pop();
            switch (event) {
                case InitEvent initEvent -> {
                    if (initEvent.getSource() != null) {
                        int[] source = initEvent.getSource();
                        for (int i = 0; i < source.length; i++) {
                            eventDeque.push(new DetermineMaxNumAtIndexEvent(source, i));
                        }
                    }
                }
                case DetermineMaxNumAtIndexEvent determineMaxNumAtIndexEvent -> {
                    int[] source = determineMaxNumAtIndexEvent.getSource();
                    int index = determineMaxNumAtIndexEvent.getIndex();
                    MaxIndexContext maxIndexContext = new MaxIndexContext(index);
                    eventDeque.push(new SwapEvent(source, index, maxIndexContext));
                    for (int i = 0; i <= index; i++) {
                        eventDeque.push(new UpdateMaxIndexEvent(source, i, maxIndexContext));
                    }
                }
                case UpdateMaxIndexEvent updateMaxIndexEvent -> {
                    int[] source = updateMaxIndexEvent.getSource();
                    int toCompareIndex = updateMaxIndexEvent.getToCompareIndex();
                    int maxIndex = updateMaxIndexEvent.getMaxIndexContext().getMaxIndex();
                    if (source[toCompareIndex] > source[maxIndex]) {
                        updateMaxIndexEvent.getMaxIndexContext().setMaxIndex(toCompareIndex);
                    }
                }
                case SwapEvent swapEvent -> {
                    ArrayUtils.swap(swapEvent.getSource(), swapEvent.getIndex(), swapEvent.getMaxIndexContext().getMaxIndex());
                }
                default -> throw new IllegalStateException("Unexpected value: " + event);
            }
        }
    }

    private static abstract class Event {
        int[] source;

        public Event(int[] source) {
            this.source = source;
        }

        public int[] getSource() {
            return this.source;
        }
    }

    private static class InitEvent extends Event {
        public InitEvent(int[] source) {
            super(source);
        }
    }


    private static class DetermineMaxNumAtIndexEvent extends Event {
        int index;

        public DetermineMaxNumAtIndexEvent(int[] source, int index) {
            super(source);
            this.index = index;
        }

        public int getIndex() {
            return index;
        }
    }

    private static class UpdateMaxIndexEvent extends Event {
        int toCompareIndex;
        MaxIndexContext maxIndexContext;

        public UpdateMaxIndexEvent(int[] source, int toCompareIndex, MaxIndexContext maxIndexContext) {
            super(source);
            this.toCompareIndex = toCompareIndex;
            this.maxIndexContext = maxIndexContext;
        }

        public int getToCompareIndex() {
            return toCompareIndex;
        }

        public MaxIndexContext getMaxIndexContext() {
            return maxIndexContext;
        }
    }

    private static class MaxIndexContext {
        int maxIndex;

        public int getMaxIndex() {
            return maxIndex;
        }

        public void setMaxIndex(int maxIndex) {
            this.maxIndex = maxIndex;
        }

        public MaxIndexContext(int maxIndex) {
            this.maxIndex = maxIndex;
        }
    }

    private static class SwapEvent extends Event {
        int index;
        MaxIndexContext maxIndexContext;

        public SwapEvent(int[] source, int index, MaxIndexContext maxIndexContext) {
            super(source);
            this.index = index;
            this.maxIndexContext = maxIndexContext;
        }

        public int getIndex() {
            return index;
        }

        public void setIndex(int index) {
            this.index = index;
        }

        public MaxIndexContext getMaxIndexContext() {
            return maxIndexContext;
        }

        public void setMaxIndexContext(MaxIndexContext maxIndexContext) {
            this.maxIndexContext = maxIndexContext;
        }
    }
}
